package ar.edu.unicen.exa.isistan.archassis.diff;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Vector;

import jdiff.JDiffAntTask.ProjectInfo;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Javadoc;
import org.apache.tools.ant.taskdefs.Javadoc.DocletInfo;
import org.apache.tools.ant.taskdefs.Javadoc.DocletParam;
import org.apache.tools.ant.types.DirSet;
import org.apache.tools.ant.types.Path;

import ar.edu.unicen.exa.isistan.archassis.util.MiscUtils;

public class JDiffExecute {
	/** 
	 * Ferward or backward slash, as appropriate.
	 */
	static String DIR_SEP = System.getProperty("file.separator");
	
	
	public static void execute(JDiffData jdd) {
		// Call Javadoc twice to generate Javadoc for each project
		log("Generando JavaDoc OLD",0);
		//generateJavadoc(jdd.getConfiguredOld(), jdd.getProject(), jdd.getSource(), jdd.getDestdir());
		log("Generando JavaDoc NEW",0);
		//generateJavadoc(jdd.getConfiguredNew(), jdd.getProject(), jdd.getSource(), jdd.getDestdir());

		// Call Javadoc three times for JDiff.
		log("Generando XML OLD",0);
		generateXML(jdd.getConfiguredOld(), jdd.getProject(), jdd.getSource(), jdd.getDestdir(), jdd.getJdiffClassPath());
		log("Generando XML NEW",0);
		generateXML(jdd.getConfiguredNew(), jdd.getProject(), jdd.getSource(), jdd.getDestdir(), jdd.getJdiffClassPath());
		
		log("Comparando",0);
//		compareXML(jdd.getConfiguredOld().getName(), jdd.getConfiguredNew().getName(), jdd.getProject(), 
//				jdd.getSource(), jdd.getDestdir(), jdd.getJdiffHome(), jdd.getJdiffClassPath(),
//				jdd.getStats(), jdd.getDocchanges());
	}
	
	/**
	 * Generate the Javadoc for the project. If you want to generate
	 * the Javadoc report for the project with different parameters from the
	 * simple ones used here, then use the Javadoc Ant task directly, and 
	 * set the javadoc attribute to the "old" or "new" element.
	 *
	 * @param proj The current Project
	 */
	private static void generateJavadoc(ProjectInfo proj, Project project, String source, File destDir) {	
		String javadoc = proj.getJavadoc();
		if (javadoc != null && javadoc.compareTo("generated") != 0) {
			log("Configured to use existing Javadoc located in " +  
					javadoc, Project.MSG_INFO);
			return;
		}

		String apiname = proj.getName();
		Javadoc jd = initJavadoc("Javadoc for " + apiname, project, source);
		jd.setDestdir(new File(destDir.toString() + DIR_SEP + apiname));
		addSourcePaths(jd, proj, project);

		jd.setPrivate(true);
		jd.setPackagenames(getPackageList(proj, project));

		// Execute the Javadoc command to generate a regular Javadoc report
		jd.perform();
	}
	
	/**
	 * Create a fresh new Javadoc task object and initialize it.
	 *
	 * @param logMsg String which appears as a prefix in the Ant log
	 * @return The new task.Javadoc object
	 */
	protected static Javadoc initJavadoc(String logMsg, Project project, String source) {
		Javadoc jd = new Javadoc();
		jd.setProject(project); // Vital, otherwise Ant crashes
		jd.setTaskName(logMsg);
		jd.setSource(source); // So we can set the language version
		jd.init();

		// Set up some common parameters for the Javadoc task
		//if (verboseAnt) {
			jd.setVerbose(true);
		//}
		return jd;
	}
	
	/**
	 * Add the root directories for the given project to the Javadoc 
	 * sourcepath.
	 */
	protected static void addSourcePaths(Javadoc jd, ProjectInfo proj, Project project) {
		Vector dirSets = proj.getDirsets();
		int numDirSets = dirSets.size();
		for (int i = 0; i < numDirSets; i++) {
			DirSet dirSet = (DirSet)dirSets.elementAt(i);
			jd.setSourcepath(new Path(project, dirSet.getDir(project).toString()));
		}
	}
	
	/**
	 * Return the comma-separated list of packages. The list is
	 * generated from Ant DirSet tasks, and includes all directories
	 * in a hierarchy, e.g. com, com/acme. com/acme/foo. Duplicates are 
	 * ignored.
	 */
	protected static String getPackageList(ProjectInfo proj, Project project) throws BuildException {
		String packageList = ""; 
		java.lang.StringBuffer sb = new StringBuffer();
		Vector dirSets = proj.getDirsets();
		int numDirSets = dirSets.size();
		boolean addComma = false;
		for (int i = 0; i < numDirSets; i++) {
			DirSet dirSet = (DirSet)dirSets.elementAt(i);
			DirectoryScanner dirScanner = dirSet.getDirectoryScanner(project);
			String[] files = dirScanner.getIncludedDirectories();
			for (int j = 0; j < files.length; j++) {
				if (!addComma){
					addComma = true;
				} else {
					sb.append(",");
				}
				sb.append(files[j]);
			}
		}
		packageList = sb.toString();
		if (packageList.compareTo("") == 0) {
			throw new BuildException("Error: no packages found to scan");
		}
		log(" Package list: " + packageList, Project.MSG_INFO);

		return packageList;
	}
	
	/**
	 * Convenient method to create a Javadoc task, configure it and run it
	 * to generate the XML representation of a project's source files.
	 *
	 * @param proj The current Project
	 */
	protected static void generateXML(ProjectInfo proj, Project project, String source, File destDir, String jdiffClassPath) {
		String apiname = proj.getName();
		Javadoc jd = initJavadoc("Analyzing " + apiname, project, source);
		jd.setDestdir(destDir);
		addSourcePaths(jd, proj, project);

		// Tell Javadoc which packages we want to scan. 
		// JDiff works with packagenames, not sourcefiles.
		jd.setPackagenames(getPackageList(proj, project));

		// Create the DocletInfo first so we have a way to use it to add params
		DocletInfo dInfo = jd.createDoclet();
		jd.setDoclet("jdiff.JDiff");
		jd.setDocletPath(new Path(project, jdiffClassPath));

		// Now set up some parameters for the JDiff doclet.
		DocletParam dp1 = dInfo.createParam();
		dp1.setName("-apiname");
		dp1.setValue(apiname);
		DocletParam dp2 = dInfo.createParam();
		dp2.setName("-baseURI");
		dp2.setValue("http://www.w3.org");
		// Put the generated file in the same directory as the report
		DocletParam dp3 = dInfo.createParam();
		dp3.setName("-apidir");
		dp3.setValue(destDir.toString());

		// Execute the Javadoc command to generate the XML file.
		jd.perform();
	}
	
	/**
	 * Convenient method to create a Javadoc task, configure it and run it
	 * to compare the XML representations of two instances of a project's 
	 * source files, and generate an HTML report summarizing the differences.
	 *
	 * @param oldapiname The name of the older version of the project
	 * @param newapiname The name of the newer version of the project
	 */
	protected static void compareXML(String oldapiname, String newapiname, Project project, String source, File destDir,
			String jdiffHome, String jdiffClassPath, boolean stats, boolean docChanges) {
		System.out.println(jdiffClassPath);
		Javadoc jd = initJavadoc("Comparing versions", project, source);
		jd.setDestdir(destDir);
		jd.setPrivate(true);

		// Tell Javadoc which files we want to scan - a dummy file in this case
		jd.setSourcefiles(MiscUtils.checkFile(jdiffHome + DIR_SEP + "Null.java"));

		// Create the DocletInfo first so we have a way to use it to add params
		DocletInfo dInfo = jd.createDoclet();
		jd.setDoclet("jdiff.JDiff");
		jd.setDocletPath(new Path(project, jdiffClassPath));

		// Now set up some parameters for the JDiff doclet.
		DocletParam dp1 = dInfo.createParam();
		dp1.setName("-oldapi");
		dp1.setValue(oldapiname);
		DocletParam dp2 = dInfo.createParam();
		dp2.setName("-newapi");
		dp2.setValue(newapiname);
		// Get the generated XML files from the same directory as the report
		DocletParam dp3 = dInfo.createParam();
		dp3.setName("-oldapidir");
		dp3.setValue(destDir.toString());
		DocletParam dp4 = dInfo.createParam();
		dp4.setName("-newapidir");
		dp4.setValue(destDir.toString());

		// Assume that Javadoc reports already exist in ../"apiname"
		DocletParam dp5 = dInfo.createParam();
		dp5.setName("-javadocold");
		dp5.setValue(".." + DIR_SEP + oldapiname + DIR_SEP);
		DocletParam dp6 = dInfo.createParam();
		dp6.setName("-javadocnew");
		dp6.setValue(".." + DIR_SEP + newapiname + DIR_SEP);

		if (stats) {
			// There are no arguments to this argument
			dInfo.createParam().setName("-stats");
			// We also have to copy two image files for the stats pages
			copyFile(jdiffHome + DIR_SEP + "black.gif",
					destDir.toString() + DIR_SEP + "black.gif", destDir);
			copyFile(jdiffHome + DIR_SEP + "background.gif",
					destDir.toString() + DIR_SEP + "background.gif", destDir);
		}

		if (docChanges) {
			// There are no arguments to this argument
			dInfo.createParam().setName("-docchanges");
		}

		project.log("gol");
		// Execute the Javadoc command to compare the two XML files
		log("ejecutando", 0);
		log(jd.toString(),1);
		jd.perform();
	}
	
	/**
	 * Copy a file from src to dst. Also checks that "destdir/changes" exists
	 */
	private static void copyFile(String src, String dst, File destDir){
//		File srcFile = new File(src);
//		File dstFile = new File(dst);
		try {
			File reportSubdir = new File(destDir.toString() + 
					DIR_SEP + "changes");
			if (!reportSubdir.mkdir() && !reportSubdir.exists()) {
				log("Warning: unable to create " + reportSubdir,
						Project.MSG_WARN);
			}

			InputStream in = new FileInputStream(src);
			OutputStream out = new FileOutputStream(dst);

			// Transfer bytes from in to out
			byte[] buf = new byte[1024];
			int len;
			while ((len = in.read(buf)) > 0) {
				out.write(buf, 0, len);
			}
			in.close();
			out.close();
		} catch (java.io.FileNotFoundException fnfe) {
			log("Warning: unable to copy " + src.toString() + 
					" to " + dst.toString(), Project.MSG_WARN);
			// Discard the exception
		} catch (java.io.IOException ioe) {
			log("Warning: unable to copy " + src.toString() + 
					" to " + dst.toString(), Project.MSG_WARN);
			// Discard the exception
		}
	}
	
	private static void log(String msj, int level) {
		System.out.println(msj);
	}
}
